package jrain.fw.id.snowflake;

import jrain.fw.core.log.Logger;
import jrain.fw.core.log.LoggerFactory;
import jrain.utils.exception.ServiceException;

/**
 * <pre>
 * 作者：489088214@qq.com 
 * 描述：Id生成器
 * </pre>
 */
public class IdWorker {

	private static Logger log = LoggerFactory.getLogger("cs-id");

	private final long workerId;
	private final long epoch = 1403854494756L;
	private final long workerIdBits = 10L;
	// 机器ID最大值 1023
	private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;
	// 0，并发控制
	private long sequence = 0L;
	// 毫秒内自增位
	private final long sequenceBits = 12L;
	// 42
	private final long workerIdShift = this.sequenceBits;
	// 22
	private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;
	// 4095,111111111111,12位
	private final long sequenceMask = -1L ^ -1L << this.sequenceBits;
	private long lastTimestamp = -1L;

	public IdWorker(long workerId) {
		if (workerId > this.maxWorkerId || workerId < 0) {
			String emsg = "The number of services should be between [%s,%s],%s";
			String msg = String.format(emsg, 0, this.maxWorkerId, workerId);
			throw new ServiceException(msg);
		}
		this.workerId = workerId;
	}

	public synchronized long nextId() {
		long timestamp = timeGen();
		// 如果上一个timestamp与新产生的相等，则sequence加一(0-4095循环);
		if (this.lastTimestamp == timestamp) {
			// 对新的timestamp，sequence从0开始
			this.sequence = this.sequence + 1 & this.sequenceMask;
			if (this.sequence == 0) {
				// 重新生成timestamp
				timestamp = this.tilNextMillis(this.lastTimestamp);
			}
		} else {
			this.sequence = 0;
		}
		if (timestamp < this.lastTimestamp) {
			String msg = "Clock error,Clock greater than %d !";
			msg = String.format(msg, (this.lastTimestamp - timestamp));
			log.error(msg);
			throw new ServiceException(msg);
		}

		this.lastTimestamp = timestamp;
		return timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence;
	}

	/**
	 * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	/**
	 * 获得系统当前毫秒数
	 */
	private static long timeGen() {
		return System.currentTimeMillis();
	}

}